package me.itsoo.artemis.framework.core;

import me.itsoo.artemis.framework.core.util.ArrayUtils;
import me.itsoo.artemis.framework.core.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * FolderClassResource
 *
 * @author zxy
 * @version 0.0.1
 * @since 2023/6/5
 */
public class FolderClassResource implements Resource {

    private static final String FILE_TYPE = "file";

    private static final String JAR_TYPE = "jar";

    private static final String CLASS_FILE = ".class";

    private static final String CLASS_OUTPUT_FOLDER = "classes/";

    private final String basePath;

    public FolderClassResource(String basePath) {
        this.basePath = basePath;
    }

    @Override
    public boolean isFile() {
        return false;
    }

    @Override
    public String[] getFilenames() {
        final Set<String> classNames = getClassNames(basePath);
        return classNames.toArray(new String[0]);
    }

    private Set<String> getClassNames(String basePath) {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String basePathPath = StringUtils.replace(basePath, ".", "/");
        final URL url = classLoader.getResource(basePathPath);

        if (Objects.nonNull(url)) {
            final String protocol = url.getProtocol();
            if (FILE_TYPE.equals(protocol)) {
                return getClassNamesFromDir(url.getPath(), basePath);
            } else if (JAR_TYPE.equals(protocol)) {
                try (JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile()) {
                    return getClassNamesFromJar(jar.entries(), basePath);
                } catch (Exception ignore) { // NOSONAR
                }
            }
            return Collections.emptySet();
        } else {
            return getClassNamesFromJars(((URLClassLoader) classLoader).getURLs(), basePath);
        }
    }

    /**
     * 获取目录下全部类名
     *
     * @param filePath String
     * @param basePath String
     * @return Set
     */
    private Set<String> getClassNamesFromDir(String filePath, String basePath) {
        final Set<String> result = new HashSet<>();
        final File[] files = (new File(filePath)).listFiles();
        if (ArrayUtils.isNotEmpty(files)) {
            for (File childFile : files) {
                if (childFile.isDirectory()) {
                    result.addAll(getClassNamesFromDir(childFile.getPath(), basePath + "." + childFile.getName()));
                } else {
                    if (isClassFile(childFile.getName())) {
                        final String className = StringUtils.replace(childFile.getName(), CLASS_FILE, "");
                        result.add(basePath + "." + className);
                    }
                }
            }
        }

        return result;
    }

    /**
     * 获取 url 包下全部类名
     *
     * @param urls     URL[]
     * @param basePath String
     * @return Set
     */
    private Set<String> getClassNamesFromJars(URL[] urls, String basePath) {
        final Set<String> result = new HashSet<>();
        for (URL url : urls) {
            final String classPath = url.getPath();
            if (!classPath.endsWith(CLASS_OUTPUT_FOLDER)) {
                try (JarFile jar = new JarFile(classPath.substring(classPath.indexOf("/")))) {
                    result.addAll(getClassNamesFromJar(jar.entries(), basePath));
                } catch (IOException ignore) { // NOSONAR
                }
            }
        }

        return result;
    }

    /**
     * 获取 jar 包下全部类名
     *
     * @param jars     Enumeration
     * @param basePath String
     * @return Set
     */
    private Set<String> getClassNamesFromJar(Enumeration<JarEntry> jars, String basePath) {
        final Set<String> result = new HashSet<>();
        while (jars.hasMoreElements()) {
            final JarEntry jar = jars.nextElement();
            if (!jar.isDirectory() && (isClassFile(jar.getName()))) {
                final String entryName = StringUtils.replace(jar.getName(), "/", ".");
                if (entryName.startsWith(basePath)) {
                    final String className = StringUtils.replace(entryName, CLASS_FILE, "");
                    result.add(className);
                }
            }
        }

        return result;
    }

    private boolean isClassFile(String fileName) {
        return fileName.endsWith(CLASS_FILE) && !fileName.contains("$");
    }
}
